	
#define LOG_LEVEL 0
#include <syslog.h>

#include <stdio.h>
#include <string.h>
#include <pin.h>
#include <aos/kernel.h>
#include <aos/debug.h>
#include <ulog/ulog.h>
#include <drv/codec.h>
#include "app_codec.h"
#include <aos/ringbuffer.h>

#define TAG "codec"

#define CODEC_OUTPUT_RB_SISZE (50*1024)
#define CODEC_RECV_BUFF_LEN     (40 * 2 * 16)      // 40 ms audio
static int16_t data_recv[CODEC_RECV_BUFF_LEN / 2];	//(uint8_t *)(CODEC_BUFF_ADDR);

static aos_sem_t g_sem_codec_rx;
static aos_sem_t g_sem_codec_tx;
static codec_output_t output_handle;
static codec_input_t input_handle;
static dev_ringbuf_t codec_output_rb_hdl;
static uint8_t g_codec_init_done = 0;

typedef struct {
	uint8_t *addr;
	int	size;
    long long time_stamp;
	int cap;			// capability: total size
} codec_frame_t;

typedef struct {
	codec_frame_t *cache;
	size_t	cache_size;
	int	rd;
	int wt;
	aos_mutex_t lock;
} codec_frame_ringbuff_t;

#define EVT_CODEC_QUIT      1

/************************************
 * codec ring cache 
 ************************************/
static codec_frame_ringbuff_t *g_codec_cache;
static int g_codec_input_runing = 0;
static int g_codec_output_runing = 0;
static aos_event_t g_event_codec_tsk;

static codec_frame_ringbuff_t * codec_cache_init(size_t frame_num)
{
	codec_frame_ringbuff_t *ringbuff;
	int ret;

	ringbuff = (codec_frame_ringbuff_t *)aos_zalloc_check(sizeof(codec_frame_ringbuff_t));
	ringbuff->cache = (codec_frame_t *)aos_zalloc_check(sizeof(codec_frame_t) * (frame_num + 1));
	ringbuff->cache_size = frame_num + 1;

	ret = aos_mutex_new(&ringbuff->lock);
	CHECK_RET_WITH_RET(ret == 0, NULL);

	return ringbuff;
}

static int codec_cache_reset(codec_frame_ringbuff_t *hdl)
{
	aos_check_param(hdl);

	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);
    hdl->wt = hdl->rd;
	aos_mutex_unlock(&hdl->lock);
    return 0;
}

static int codec_cache_put(codec_frame_ringbuff_t *hdl, uint8_t *data, int size, long long time_stamp)
{
	aos_check_param(hdl && data && size > 0);

	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);

	int wt = hdl->wt;
	int rd = hdl->rd;
	// static int log_ctrl = 0;

	if ((wt + 1) % hdl->cache_size == rd) {
		// if (++log_ctrl % 100 == 0) {
		// 	LOGE(TAG, "cahce full");
		// 	log_ctrl = 0;
		// }
        LOGE(TAG, "cahce full");
		hdl->rd = (rd + 1) % hdl->cache_size;	// move rd pointer to drop oldest frame
	}

	if (hdl->cache[wt].addr == NULL || hdl->cache[wt].cap < size)
	{
		hdl->cache[wt].addr = (uint8_t *)aos_realloc_check(hdl->cache[wt].addr, size);
		hdl->cache[wt].cap = size;
	}

	memcpy(hdl->cache[wt].addr, data, size);
	hdl->cache[wt].size = size;
	hdl->cache[wt].time_stamp = time_stamp;

	hdl->wt = (wt + 1) % hdl->cache_size;

	aos_mutex_unlock(&hdl->lock);

	return 0;
}

static int codec_cache_get(codec_frame_ringbuff_t *hdl, long long *time_stamp, uint8_t *data, int max_size)
{
	aos_check_param(hdl && data && max_size > 0);
	
	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);

	int wt = hdl->wt;
	int rd = hdl->rd;
    int read_size = 0;

	if (wt == rd) {
		aos_mutex_unlock(&hdl->lock);
        // *data = NULL;
		return -1;
	}

    if (max_size < hdl->cache[rd].size) {
        LOGE(TAG, "codec over size size=%d, in_size=%d", hdl->cache[rd].size, max_size);
		aos_mutex_unlock(&hdl->lock);
		return -1;
    }

    memcpy(data, hdl->cache[rd].addr, hdl->cache[rd].size);
	read_size = hdl->cache[rd].size;

	if (time_stamp) {
		*time_stamp = hdl->cache[rd].time_stamp;
	}

    hdl->cache[rd].size = 0;
    hdl->rd = (rd + 1) % hdl->cache_size;

    aos_mutex_unlock(&hdl->lock);

	return read_size;
}

// static void codec_cache_get_done(codec_frame_ringbuff_t *hdl)
// {
// 	aos_check_param(hdl);

// 	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);

// 	int wt = hdl->wt;
// 	int rd = hdl->rd;

// 	if (wt != rd) {
// 		hdl->cache[rd].size = 0;
// 		hdl->rd = (rd + 1) % hdl->cache_size;
// 	}

//     aos_mutex_unlock(&hdl->lock);
// }
#if 0
static int codec_cache_available_read(codec_frame_ringbuff_t *hdl)
{
	aos_check_param(hdl);

	int wt = hdl->wt;
	int rd = hdl->rd;
	size_t size = hdl->cache_size;

	return wt >= rd ? wt - rd : wt + (size - rd);
}

static int codec_cache_full(codec_frame_ringbuff_t *hdl)
{
	aos_check_param(hdl);

	int wt = hdl->wt;
	int rd = hdl->rd;

    return (wt + 1) % hdl->cache_size == rd;
}
#endif


/************************************
 * codec input/output wrapper 
 ************************************/
static void player_cb(int idx, codec_event_t event, void *arg)
{
    if (event == CODEC_EVENT_PERIOD_WRITE_COMPLETE || event == CODEC_EVENT_WRITE_BUFFER_EMPTY) {
        // cb_output_transfer_flag++;
        aos_sem_signal(&g_sem_codec_tx);
    }
}

static void record_cb(int idx, codec_event_t event, void *arg)
{
    if ((event == CODEC_EVENT_PERIOD_READ_COMPLETE) || (event == CODEC_EVENT_READ_BUFFER_FULL)) {
        // cb_input_transfer_flag++;
        aos_sem_signal(&g_sem_codec_rx);
    }
}

static int player_init(void)
{
    aos_sem_new(&g_sem_codec_tx, 0);

    codec_output_config_t config;
    config.bit_width = 16;
    config.mono_mode_en = 1;
    config.sample_rate = 16000;

    int tx_buf_length = 2 * (config.sample_rate / 1000) * 400;

    output_handle.buf = (uint8_t *)aos_malloc(tx_buf_length);
    output_handle.buf_size = tx_buf_length;
    output_handle.cb = player_cb;
    output_handle.cb_arg = NULL;
    output_handle.ch_idx = 0;
    output_handle.codec_idx = 0;
    output_handle.period = tx_buf_length / 2;

    int ret = csi_codec_output_open(&output_handle);

    if (ret != 0) {
        printf("codec output open error\n");
        return -1;
    }

    ret = csi_codec_output_config(&output_handle, &config);

    return 0;
}

static int record_init(void)
{
    aos_sem_new(&g_sem_codec_rx, 0);

    codec_input_config_t config;
    config.bit_width = 16;
    config.channel_num = 1;
    config.sample_rate = 16000;

    int rx_buf_length = 2 * (config.sample_rate / 1000) * 80;

    input_handle.buf = (uint8_t *)aos_malloc(rx_buf_length);
    input_handle.buf_size = rx_buf_length;
    input_handle.cb = record_cb;
    input_handle.cb_arg = NULL;
    input_handle.ch_idx = 0;
    input_handle.codec_idx = 0;
    input_handle.period = rx_buf_length / 2;

    int ret = csi_codec_input_open(&input_handle);

    if (ret != 0) {
        printf("codec input open error\n");
        return -1;
    }

    ret = csi_codec_input_config(&input_handle, &config);

    return ret;
}

static void player_deinit(void)
{
    printf("player end\n");

    int ret = csi_codec_output_close(&output_handle);

    if (ret != 0) {
        printf("codec output close error\n");
        return;
    }
}

static void record_deinit(void)
{
    printf("record end\n");

    int ret = csi_codec_input_close(&input_handle);

    if (ret != 0)
    {
        printf("codec input close error\n");
        return;
    }
}

void codec_input_task(void *arg)
{
    int length;
    long long last_time_stamp;
    long long cur_time_stamp = 0;
    long long time_stamp_offset = 0;  // make sure time stamp starts from zero

    csi_codec_input_start(&input_handle);
    // csi_codec_output_start(&output_handle);

    while (g_codec_input_runing) {
        aos_sem_wait(&g_sem_codec_rx, 5000);
        length = csi_codec_input_read(&input_handle, (uint8_t *)data_recv, CODEC_RECV_BUFF_LEN);
        if (length > 0) {
            if (cur_time_stamp == 0) {
                /* first time, time_stamp_offset should be zero + compensation 3 ms */
                LOGD(TAG, "first audio frame");
                last_time_stamp = aos_now_ms();
                cur_time_stamp = last_time_stamp + 3;
            } else {
                cur_time_stamp = aos_now_ms();
            }

            time_stamp_offset += cur_time_stamp - last_time_stamp;
            last_time_stamp = cur_time_stamp;
            codec_cache_put(g_codec_cache, (uint8_t *)data_recv, length, time_stamp_offset);
        }
    }

    csi_codec_input_stop(&input_handle);
    // csi_codec_output_stop(&output_handle);

    aos_event_set(&g_event_codec_tsk, EVT_CODEC_QUIT, AOS_EVENT_OR);
}

int app_codec_init()
{
    if(g_codec_init_done == 0) {
        g_codec_cache = codec_cache_init(25);

        csi_codec_init(0);
        player_init();
        record_init();

        aos_event_new(&g_event_codec_tsk, 0);

        g_codec_init_done =1;
    }
    return 0;
}

int app_codec_deinit()
{
    if (g_codec_init_done) {
        app_codec_input_stop();

        record_deinit();
        player_deinit();
        csi_codec_uninit(0);

        aos_event_free(&g_event_codec_tsk);

        g_codec_init_done =0;
    }
    return 0;
}


int app_codec_input_start()
{
    aos_task_t tsk_codec_input;

    if(g_codec_init_done == 0) {
        LOGE(TAG, "codec not init\n");
        return -1;
    }

    if (g_codec_input_runing) {
        LOGW(TAG, "codec already started");
        return -1;
    }

    g_codec_input_runing = 1;
    codec_cache_reset(g_codec_cache);
    
    aos_task_new_ext(&tsk_codec_input, "codecin", codec_input_task, NULL, 4096, AOS_DEFAULT_APP_PRI - 10);

    return 0;
}

int app_codec_input_stop(void)
{
    unsigned int flags;

    if (g_codec_input_runing) {
        g_codec_input_runing = 0;

        aos_event_get(&g_event_codec_tsk, EVT_CODEC_QUIT, AOS_EVENT_OR_CLEAR, &flags, AOS_WAIT_FOREVER);
    }

    return 0;
}

void codec_output_task(void *arg)
{
    int offset = 0;
    int length = output_handle.period;
    uint8_t *codec_out_buff = (uint8_t *)malloc(length);
    while(g_codec_output_runing) {
        offset = 0;
        int rb_count = ringbuffer_available_read_space(&codec_output_rb_hdl);
        if(rb_count < length) {
            aos_msleep(100);
            continue;
        }
        ringbuffer_read(&codec_output_rb_hdl, codec_out_buff, length);
        while (offset < length) {
            offset += csi_codec_output_write(&output_handle, (uint8_t *)(codec_out_buff + offset), length - offset);
            aos_sem_wait(&g_sem_codec_tx, 1000);
        }
    }
    free(codec_out_buff);
}
void app_codec_output_start(void)
{
    char *rb_buffer;
    aos_task_t tsk_codec_output;
    rb_buffer = malloc(CODEC_OUTPUT_RB_SISZE);
    ringbuffer_create(&codec_output_rb_hdl, rb_buffer, CODEC_OUTPUT_RB_SISZE);
    g_codec_output_runing = 1;
    aos_task_new_ext(&tsk_codec_output, "codecout", codec_output_task, NULL, 4096, AOS_DEFAULT_APP_PRI);
    csi_codec_output_start(&output_handle);
}

void app_codec_output_write(const uint8_t *data, uint32_t length)
{
    int write_space = ringbuffer_available_write_space(&codec_output_rb_hdl);
    if (write_space < length) {
        LOGW(TAG, "codec output rb full\r\n");
        return;
    }
    ringbuffer_write(&codec_output_rb_hdl, (uint8_t *)data, length);
}

void app_codec_output_stop(void)
{
    g_codec_output_runing = 0;
    free(codec_output_rb_hdl.buffer);
    csi_codec_output_stop(&output_handle);
}

int app_codec_input_read(long long *time_stamp, uint8_t *data, int size)
{
    return codec_cache_get(g_codec_cache, time_stamp, data, size);
}

